package pseudoAssemblerCompiler.twopassassembler.model.assemblerAlgorithms;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import pseudoAssemblerCompiler.twopassassembler.common.NotificationCenter;
import pseudoAssemblerCompiler.twopassassembler.controller.Constants;
import pseudoAssemblerCompiler.twopassassembler.exceptions.RequiredDataNotSetException;
import pseudoAssemblerCompiler.twopassassembler.model.Utils;
import pseudoAssemblerCompiler.twopassassembler.model.codeoperationtable.ICodeOperationTableModel;
import pseudoAssemblerCompiler.twopassassembler.model.entities.OperationCode;
import pseudoAssemblerCompiler.twopassassembler.model.entities.ReferenceTableEntity;
import pseudoAssemblerCompiler.twopassassembler.model.referencetable.IReferenceTableModel;
import pseudoAssemblerCompiler.twopassassembler.model.symbolnametable.ISymbolicNamesTableModel;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Общие методы которые есть во все алгоритмах "ассемблера"
 *
 * User: Администратор
 * Date: 20.08.12
 * Time: 13:24
 */
public abstract class AbstractAssembler implements Assembler, IViewModel {

    protected final Logger LOGGER = LoggerFactory.getLogger(this.getClass());

    protected StringBuilder _errorBuffer = new StringBuilder();

    protected StringBuilder _infoBuffer = new StringBuilder();

    protected boolean _hasFirstPassErrors = false;

    protected boolean _hasSecondPassErrors = false;

    /**
     * Таблица кодов операций
     */
    protected ICodeOperationTableModel _codeOperationTable;

    /**
     * Таблица символических имен
     */
    protected ISymbolicNamesTableModel _symbolicNamesTable;

    /**
     * Вспомогательная таблица
     */
    protected IReferenceTableModel _referenceTable;

    protected NotificationCenter _notificationCenter;

    /**
     * Создание строки адреса
     */
    protected StringBuilder _addrBuilder;

    /**
     * Счетчик адресов
     */
    protected int _addrCounter;

    /**
     * Счетчик строки кода программы
     */
    protected int lineCount = 0;

    public int start;
    public int length;
    protected int _programmCodelength = 0;

    protected AbstractAssembler(ICodeOperationTableModel codeOperationTable,
                          ISymbolicNamesTableModel symbolicNamesTable,
                          IReferenceTableModel referenceTable) {
        _codeOperationTable = codeOperationTable;
        _symbolicNamesTable = symbolicNamesTable;
        _referenceTable = referenceTable;
        _notificationCenter = NotificationCenter.getDefaultCenter();
    }

    @Override
    public boolean hasFirstPassErrors() {
        return _hasFirstPassErrors;
    }

    @Override
    public boolean hasSecondPassErrors() {
        return _hasSecondPassErrors;
    }

    @Override
    public int getStart() {
        return start;
    }

    @Override
    public int getLength() {
        return length;
    }

    @Override
    public String getInfoBuffer() {
        return _infoBuffer.toString();
    }

    @Override
    public String getErrorBuffer() {
        return _errorBuffer.toString();
    }

    protected void clearBuffers() {
        if(_infoBuffer != null) {
            _infoBuffer.setLength(0);
        }
        if(_errorBuffer != null) {
            _errorBuffer.setLength(0);
        }
    }

    protected void clearCounters(){
        lineCount = 0;
        _programmCodelength = 0;
        start = 0;
        length = 0;
    }

    protected boolean _isFirstPass;

    protected void notifyPublishers(String msg, Constants constant) {
        if(constant == Constants.OperationCodeController.ERROR) {
            _hasFirstPassErrors = true;
            _errorBuffer.append(String.format("строка: %d - %s", lineCount, msg));
        } else {
            _hasFirstPassErrors = false;
            _infoBuffer.append(msg);
        }
        LOGGER.debug(msg);
        _notificationCenter.postNotification(this, constant);
    }

    protected void notifyPublishers(String msg, Constants constant, ReferenceTableEntity entity) {
        if(constant == Constants.OperationCodeController.ERROR) {
            _hasFirstPassErrors = true;
            _errorBuffer.append(String.format("строка: %d - %s", entity.get_lineNumber(), msg));
        } else {
            _hasFirstPassErrors = false;
            _infoBuffer.append(msg);
        }
        LOGGER.debug(msg);
        _notificationCenter.postNotification(this, constant);
    }

    /**
     * Создаёт строку адреса и дописывает недостоющие нули
     * @param aNumberToAppend
     * @param amountOfAppendZero
     * @return
     */
    protected String addrBuild(int aNumberToAppend, int amountOfAppendZero) {
        _addrBuilder.setLength(0);
        String addr = Integer.toHexString(aNumberToAppend);
        for (int i = 0; i < (amountOfAppendZero - addr.length()); i++) {
            _addrBuilder.append('0');
        }
        _addrBuilder.append(addr);
        addr = _addrBuilder.toString();
        _addrBuilder.delete(0, amountOfAppendZero); //очищаем строку для нового адреса
        return addr;
    }

    protected String addrBuild(String hexNumber, int amountOfAppendZero) {
        String addr = hexNumber;
        for (int i = 0; i < (amountOfAppendZero - addr.length()); i++) {
            _addrBuilder.append('0');
        }
        _addrBuilder.append(addr);
        addr = _addrBuilder.toString();
        _addrBuilder.delete(0, amountOfAppendZero); //очищаем строку для нового адреса
        return addr;
    }

    protected Pattern pattern;
    protected Matcher matcher;

    protected void manageDirectiv(String[] aParam, OperationCode operationCode, String currentLine)
            throws RequiredDataNotSetException {
        if(null == operationCode) {
            String msg = String.format("Передан нулевой код операции! operationCode = [%s]", operationCode);
            notifyPublishers(msg, Constants.OperationCodeController.ERROR);
            throw new RequiredDataNotSetException(msg);
        }

        if ("RESB".equalsIgnoreCase(aParam[0]) || "RESW".equalsIgnoreCase(aParam[0])) {
            int number;
            try {
                number = Integer.parseInt(aParam[1]);
                if(0 >= number || 99999 < number) {
                    throw new NumberFormatException(aParam[1]);
                }
                _addrCounter += (number * operationCode.get_lengthOfCommand());
            } catch (NumberFormatException ex) {
                showError(aParam[1], currentLine);
                String msg = String.format("Значение %s не является числом или оно слишком большое, или <= нулю!",
                        aParam[1]);
                notifyPublishers(msg, Constants.OperationCodeController.ERROR);
                throw new RequiredDataNotSetException(aParam[1]);
            }
        }

        if ("BYTE".equalsIgnoreCase(aParam[0])) {
            String operand = aParam[1].trim();
            int amountOfByte = operand.length() - 3;
            if ((operand.startsWith("x'") || operand.startsWith("X'")) && operand.endsWith("'")) {
                amountOfByte /= 2;
            } else if ((operand.startsWith("c'") || operand.startsWith("C'")) && operand.endsWith("'")) {
                //do nothing
            } else {
                int number;
                try {
                    number = Integer.parseInt(aParam[1]);
                    if(0 >= number || 99999 < number) {
                        throw new NumberFormatException(aParam[1]);
                    }
                } catch (NumberFormatException ex) {
                    showError(aParam[1], currentLine);
                    String msg = String.format("Значение %s не является числом или оно слишком большое, или <= нулю!",
                            aParam[1]);

                    notifyPublishers(msg, Constants.OperationCodeController.ERROR);
                    throw new RequiredDataNotSetException(aParam[1]);
                }
                amountOfByte = 1;
            }
            _addrCounter += amountOfByte * operationCode.get_lengthOfCommand();
        }

        if ("WORD".equalsIgnoreCase(aParam[0])) {
            _addrCounter += operationCode.get_lengthOfCommand();
        }
    }

    protected void showError(String strPattern, String data) {
        pattern = pattern.compile(strPattern);
        matcher = pattern.matcher(data);
        while (matcher.find()) {
            length = _programmCodelength - data.length() - System.getProperty("line.separator").length();
            start = length + matcher.start();
            length = matcher.end() - matcher.start();
        }
    }

    protected void showError(String strPattern, ReferenceTableEntity entity) {
        pattern = pattern.compile(strPattern);
        String currentLine = entity.get_currentLine();
        matcher = pattern.matcher(currentLine);
        while (matcher.find()) {
            length = entity.get_currentProgramCodeLength() - currentLine.length() - System.getProperty("line.separator").length();
            start = length + matcher.start();
            length = matcher.end() - matcher.start();
        }
    }

    protected void codeOperationTableContainMnemonicCode(String mnemonicCode, ReferenceTableEntity entity)
            throws RequiredDataNotSetException {
        int symbolCount = 0;
        String refTableOperand = entity.get_operand();
        StringBuilder oper = new StringBuilder();

        if ("RESB".equalsIgnoreCase(mnemonicCode) || "RESW".equalsIgnoreCase(mnemonicCode)) {
            symbolCount = checkNumber(refTableOperand) * _codeOperationTable.getCodeOperationTableElements().get(mnemonicCode).get_lengthOfCommand() * 2;
            _infoBuffer.append(Utils.addrBuild(symbolCount, 2) + " ");
            while (symbolCount-- > 0) {
                oper.append("0");
            }
            _infoBuffer.append(oper.toString() + System.getProperty("line.separator"));
            oper.delete(0, oper.length());
        } else if("WORD".equalsIgnoreCase(mnemonicCode)) {
            symbolCount = 1 * _codeOperationTable.getCodeOperationTableElements().get(mnemonicCode).get_lengthOfCommand() * 2;
            _infoBuffer.append(Utils.addrBuild(symbolCount, 2) + " ");
            _infoBuffer.append(Utils.addrBuild(checkNumber(refTableOperand), 6) + System.getProperty("line.separator"));
        } else if("BYTE".equalsIgnoreCase(mnemonicCode)) {
            if ((refTableOperand.startsWith("x'") || refTableOperand.startsWith("X'")) && refTableOperand.endsWith("'")) {
                String subString = refTableOperand.substring(2, refTableOperand.length() - 1);
                _infoBuffer.append(Utils.addrBuild(subString.length(), 2) +
                        " " + subString + System.getProperty("line.separator"));
            } else if ((refTableOperand.startsWith("c'") || refTableOperand.startsWith("C'")) && refTableOperand.endsWith("'")) {
                String subString = refTableOperand.substring(2, refTableOperand.length() - 1);
                for (Character c : subString.toCharArray()) {
                    oper.append(Utils.addrBuild(c, 2));
                }
                _infoBuffer.append(Utils.addrBuild(subString.length(), 2) +
                        " " + oper.toString() + System.getProperty("line.separator"));
                oper.delete(0, oper.length());
            } else {
                String num = Integer.toHexString(checkNumber(refTableOperand));
                _infoBuffer.append(Utils.addrBuild(num.length(), 2) + " " + num + System.getProperty("line.separator"));
            }
        } else if("END".equalsIgnoreCase(mnemonicCode)) {
            throw new RequiredDataNotSetException("end");
        } else {

            _infoBuffer.append(mnemonicCode + " ");
            _infoBuffer.append(
                    ((_symbolicNamesTable.getSymbolNames().get(refTableOperand) == null) ?
                            refTableOperand : _symbolicNamesTable.getSymbolNames().get(refTableOperand))
                            + System.getProperty("line.separator"));
        }
    }

    private int checkNumber(String number) throws RequiredDataNotSetException {
        int n;
        try {
            n = Integer.parseInt(number);
            if(0 >= n || 99999 < n) {
                throw new NumberFormatException(number);
            }
        } catch (NumberFormatException ex) {
            throw new RequiredDataNotSetException(number);
        }
        return n;
    }

    public void printSecondPassTable() {
        for (String[] sArr : _referenceTable.getReferenceTableElements()) {
            System.out.println(sArr[0] + " " + sArr[1] + " " + sArr[2] + ";");
        }
    }

}
